[C 기술면접] 추가 빈출 질문 모음

NOTE

앞선 10개 질문 외에, C/시스템 프로그래밍 기술면접에서 자주 나오는 질문을 영역별로 정리했다. 각 항목은 한두 문장으로 먼저 답하고, 꼬리 질문이 나오면 아래 근거를 덧붙이는 식으로 외운다. 이미 정리된 개별 노트가 있으면 링크를 달았다.


1. 포인터 · 배열 · 메모리

질문답변 요지
포인터와 배열의 차이?배열은 연속된 메모리 블록(이름은 첫 원소 주소로 decay), 포인터는 주소를 담는 변수. sizeof(arr)은 전체 크기, sizeof(ptr)은 포인터 크기. 배열 이름은 대입 불가(수정 불가능한 lvalue). 함수 인자로 넘기면 포인터로 바뀌므로 길이를 따로 전달해야 한다.
const char *p, char * const p, const char * const p?각각 가리키는 값이 상수 / 포인터 자체가 상수 / 둘 다 상수. 읽는 법: * 기준 왼쪽이 값, 오른쪽이 포인터. (포인터 상수성 정리)
void *란?타입 없는 포인터. 모든 객체 포인터를 암시 변환 가능(C), 역참조·산술 불가. malloc의 반환형. C++과 달리 C에서는 캐스팅 불필요.
malloc / calloc / realloc / free 차이?malloc: 초기화 안 된 메모리. calloc(n, size): 0 초기화 + 곱셈 오버플로우 검사. realloc: 크기 변경(이동 가능) — 실패 시 NULL이고 원래 블록은 유지되므로 p = realloc(p, n)은 누수 위험 → 임시 포인터에 받아 확인. free: 해제(free(NULL)은 안전).
메모리 누수 / 댕글링 / 이중 해제?누수: 해제 안 함. 댕글링: 해제된 메모리를 가리키는 포인터 사용. 이중 해제: 같은 블록 두 번 free → 힙 손상. 대응: 소유권 규칙, free 후 NULL 대입, 생성/소멸 쌍, ASan/Valgrind. (실무 C 관례 B2~B4)
스택과 힙 차이?스택: 함수 호출 프레임(지역 변수, 자동 해제, 빠름, 크기 제한 ~8MB, 스레드별). 힙: 동적 할당(수명 수동 관리, 단편화, 느림, 크기 큼). 프로세스 메모리 레이아웃: 텍스트(코드) / 데이터(.data) / BSS(.bss) / 힙(위로 성장) / mmap 영역 / 스택(아래로 성장).
지역 변수의 주소를 반환하면?함수 종료 시 스택 프레임이 사라져 댕글링 포인터가 된다(UB). 힙 할당하거나 호출자 버퍼를 인자로 받는다.
메모리 정렬(alignment)과 구조체 패딩?CPU가 타입 크기의 배수 주소에서 효율적으로 읽도록 컴파일러가 멤버 사이에 패딩을 넣는다. 크기는 sizeof로 확인, 큰 멤버부터 선언하면 줄어든다. #pragma pack/packed는 패딩 제거(성능·정렬 문제 주의). (구조체 패딩과 멤버 정렬)
struct와 union 차이?struct: 멤버가 각자 공간(합계), union: 멤버가 같은 공간 공유(가장 큰 멤버 크기). union은 한 번에 하나만 유효, 타입 퍼닝/프로토콜 해석에 사용. (Union, Typedef, Struct)
엔디안이란?멀티바이트 정수의 바이트 저장 순서. 리틀 엔디안(x86/ARM 기본): 낮은 바이트가 낮은 주소. 네트워크는 빅 엔디안 → htons/htonl, ntohs/ntohl로 변환. (엔디안과 htons)
함수 포인터란 / 어디에 쓰나?함수의 주소를 담는 포인터. 콜백, 디스패치 테이블, 플러그인, 전략 패턴, qsort 비교 함수. int (*fp)(int, int); (함수 포인터와 디스패치 테이블)
restrict란?같은 메모리를 다른 포인터로 접근하지 않겠다는 약속 → 컴파일러 최적화 허용. 어기면 UB. memcpy(겹치면 안 됨) vs memmove(겹쳐도 됨)의 차이도 같은 맥락.
memcpy vs memmove vs memcmp?memcpy: 영역 겹침 금지. memmove: 겹쳐도 안전. memcmp: 바이트 비교(구조체 패딩 바이트 때문에 구조체 비교에 부적합).

2. 타입 · 연산 · 문법

질문답변 요지
sizeof는 함수인가?연산자(컴파일 타임, VLA 제외). 인자의 표현식은 평가되지 않는다. sizeof(int)와 sizeof arr 형태. 반환형 size_t는 %zu로 출력.
signed/unsigned 비교 함정?비교 시 signed가 unsigned로 변환되어 -1 < 1u가 거짓. size_t 루프에서 i >= 0은 항상 참(무한 루프).
정수 오버플로우?signed 오버플로우는 UB(최적화로 예상 못 한 결과), unsigned는 모듈러 wrap. 연산 전 범위 검사. (정수·타입변환 계열)
정수 승격(integer promotion)?char/short가 연산 시 int로 승격. char c = 0xFF; c << 8 같은 곳에서 부호 확장·예상 밖 결과.
==와 = 실수, 비트 연산자 우선순위?if (a & b == c)는 a & (b == c) — ==가 &보다 우선. 괄호 사용.
i++ + ++i 같은 식?같은 객체를 시퀀스 포인트 없이 두 번 수정 → UB. 인자 평가 순서도 미정의.
static 키워드의 세 가지 용도?① 지역 static: 호출 간 값 유지(수명 전체, 스코프 함수) ② 전역/함수 static: 파일 내부 링키지(은닉) ③ (C++) 클래스 멤버. (extern과 static)
volatile은 무엇을 위한가?컴파일러가 해당 변수 접근을 최적화로 생략/재배치하지 못하게(메모리 매핑 I/O, 시그널 핸들러 변수). 스레드 동기화·원자성 보장 아님.
const의 의미와 #define과의 차이?읽기 전용 변수(타입·스코프 있음). 매크로는 단순 치환.
선언(declaration)과 정의(definition)?선언은 이름/타입을 알림(extern int x;), 정의는 실제 저장 공간 할당(int x;). 정의는 하나, 선언은 여러 번 가능(ODR).
typedef와 #define의 차이?typedef는 타입 별칭(컴파일러가 처리, 포인터 타입에서 안전), #define은 텍스트 치환(#define PINT int*; PINT a, b;는 b가 int).
비트 연산 활용?플래그 설정/해제/토글/검사: `
정의되지 않은 동작(UB) / 구현 정의 / 지정되지 않은 동작?UB: 표준이 아무 요구도 안 함(최적화 가정이 깨져 임의 결과). 구현 정의: 컴파일러가 문서화해야 함(int 크기, 시프트). 지정되지 않음: 여러 합법적 결과 중 하나(인자 평가 순서). UBSan으로 탐지.

3. 문자열

질문답변 요지
strcpy / strncpy / snprintf / strlcpy 차이?strcpy: 길이 검사 없음(오버플로우). strncpy: n바이트 복사, 널 종단 보장 안 함, 남는 공간은 0으로 채움(성능 낭비). snprintf(buf, sizeof buf, "%s", src): 항상 널 종단, 가장 무난. strlcpy: 널 종단 보장(BSD/glibc 2.38+). (문자열 복사 비교)
문자열 리터럴 수정이 안 되는 이유?char *s = "abc"는 .rodata(읽기 전용)를 가리킨다. 수정하려면 char s[] = "abc"(스택에 복사본).
strlen과 sizeof 차이?strlen은 널 문자 전까지 런타임 계산, sizeof는 배열 전체 크기(널 포함, 컴파일 타임). 포인터에 sizeof는 포인터 크기.
strtok의 문제?내부 정적 상태 사용 → 스레드 안전하지 않고 원본 문자열을 수정. strtok_r 사용. (strtok_r과 sscanf)
버퍼 오버플로우를 막는 방법?길이 있는 함수 사용(snprintf, fgets), 경계 검사, -fstack-protector, ASan, 입력 검증. (메모리 경계 계열)
포맷 스트링 취약점?printf(user_input)처럼 사용자 입력을 포맷으로 쓰면 %n, %x로 메모리 읽기/쓰기 가능 → 항상 printf("%s", user_input).

4. 프로세스 · 시그널 · 스레드

질문답변 요지
프로세스와 스레드 차이?프로세스: 독립된 주소 공간·자원, 생성/문맥 전환 비용 큼, 격리 강함(IPC 필요). 스레드: 프로세스 내에서 힙·전역·파일 디스크립터 공유, 스택/레지스터만 독립, 가볍지만 한 스레드 크래시가 전체에 영향. (스레드 기본개념, IPC와 스레드 메모리 공유 비교)
fork() 동작?현재 프로세스를 복제(copy-on-write). 반환값: 부모는 자식 PID, 자식은 0, 실패 -1. 자식은 fd를 상속하며 락/스레드 상태에 주의(멀티스레드에서 fork 후에는 async-signal-safe 함수만 안전, 보통 바로 exec).
fork vs exec?fork: 복제, exec: 현재 프로세스 이미지를 새 프로그램으로 교체(PID 유지). 셸이 명령을 실행하는 방식 = fork + exec + wait.
좀비 / 고아 프로세스?좀비: 종료됐지만 부모가 wait하지 않아 프로세스 테이블에 남은 상태. 고아: 부모가 먼저 종료돼 init(1)이 입양. 좀비 방지: waitpid, SIGCHLD 핸들러, SA_NOCLDWAIT. (좀비 프로세스(fork·waitpid))
시그널 핸들러에서 주의할 점?async-signal-safe 함수만 호출(printf, malloc 금지). 공유 변수는 volatile sig_atomic_t. 핸들러는 플래그만 세우고 메인 루프에서 처리. signal() 대신 sigaction().
SIGKILL/SIGSTOP은 왜 못 잡나?커널이 프로세스를 항상 제어할 수 있어야 하므로 핸들러 등록/무시/차단 불가.
SIGPIPE란?닫힌 소켓/파이프에 write하면 발생해 프로세스가 종료. 서버는 signal(SIGPIPE, SIG_IGN) 또는 send(MSG_NOSIGNAL)로 EPIPE를 받게 처리.
재진입(reentrant) 함수란?여러 흐름(스레드/시그널)이 동시에 호출해도 안전한 함수(전역/정적 상태 없음). strtok→strtok_r, localtime→localtime_r.
스레드 안전(thread-safe)과 재진입의 차이?재진입 ⊂ 스레드 안전. 락으로 스레드 안전하게 만든 함수는 시그널 핸들러에서 재진입하면 데드락될 수 있다.
pthread_join / detach?join: 스레드 종료 대기 + 자원 회수(안 하면 좀비 스레드 누수). detach: 종료 시 자동 회수(반환값 못 받음).
스레드 간 데이터 전달 시 pthread_create 인자 주의점?지역 변수 주소를 넘기면 함수 종료 후 댕글링 → 힙에 할당한 구조체 전달, 소유권 정의. (pthread_create에 구조체 포인터 넘기기)
데드락 / 레이스 컨디션?데드락: 서로의 락을 기다려 진행 불가(4조건, 락 순서 고정으로 예방). 레이스: 실행 순서에 따라 결과가 달라지는 동기화 부재. (동기화 노트)
errno는 스레드 안전한가?스레드별(thread-local)이다. 하지만 실패한 호출 직후 바로 확인해야 한다(다음 호출이 덮어씀).
문맥 교환(context switch) 비용?레지스터·스택·페이지 테이블(프로세스 간) 교체, TLB/캐시 오염. 스레드 간이 프로세스 간보다 싸다.
가상 메모리란?프로세스마다 독립된 가상 주소 공간을 페이지 단위로 물리 메모리에 매핑(MMU/TLB). 격리·보호, 필요 시 적재(demand paging), 공유(공유 라이브러리, mmap).

5. 네트워크 · I/O

질문답변 요지
TCP 소켓 서버 흐름?서버: socket → bind → listen → accept → read/write → close. 클라이언트: socket → connect → read/write → close. (Socket 통신)
TCP vs UDP?TCP: 연결 지향, 신뢰성(재전송·순서·흐름/혼잡 제어), 바이트 스트림. UDP: 비연결, 비신뢰, 메시지(데이터그램) 경계 보존, 낮은 지연.
TCP는 메시지 경계가 없다는 뜻?send 두 번이 recv 한 번에 붙거나, 한 번이 두 번에 쪼개져 올 수 있다. 길이 헤더/구분자로 프레이밍하고 recv를 루프로 처리.
read/write가 요청한 만큼 안 읽고 쓰면?부분 읽기/쓰기가 정상이다. 루프로 남은 양을 처리하고 EINTR(시그널로 중단)은 재시도, EAGAIN(논블로킹)은 나중에.
3-way / 4-way handshake?연결: SYN → SYN+ACK → ACK. 종료: FIN → ACK → FIN → ACK(양방향 독립). TIME_WAIT는 마지막 ACK 유실 대비로 능동 종료 측에 남는다.
TIME_WAIT가 많이 쌓이면?짧은 연결을 많이 맺고 끊을 때. SO_REUSEADDR, 커넥션 재사용(Keep-Alive), 서버 쪽이 아닌 클라이언트가 먼저 닫게 설계.
SO_REUSEADDR을 쓰는 이유?서버 재시작 시 TIME_WAIT 포트에 bind가 Address already in use로 실패하는 것을 방지.
블로킹/논블로킹 소켓?블로킹은 준비될 때까지 잠듦, 논블로킹은 즉시 EAGAIN 반환. 다중화는 fd 다중화 노트.
stdio 버퍼링?printf는 라인 버퍼(터미널)/풀 버퍼(파일·파이프). 크래시 시 버퍼 내용 유실 → fflush, setvbuf, 디버그는 stderr(비버퍼). (파일 I/O 구조와 버퍼링)
파일 디스크립터와 FILE* 차이?fd: 커널 수준 정수 핸들(open/read/write). FILE*: 사용자 공간 버퍼를 가진 C 표준 라이브러리 추상화(fopen/fread).
파일 디스크립터 누수의 증상?Too many open files(EMFILE). ulimit -n, lsof -p로 확인, close 누락/예외 경로 점검.

6. 빌드 · 링크 · 도구

질문답변 요지
C 컴파일 4단계?전처리(-E) → 컴파일(-S, 어셈블리) → 어셈블(-c, 오브젝트) → 링크(실행 파일).
정적 라이브러리 vs 동적 라이브러리?정적(.a): 링크 시 코드가 실행 파일에 포함(배포 단순, 크기 큼). 동적(.so): 실행 시 로드(공유로 메모리 절약, 업데이트 용이, 실행 환경에 의존 — LD_LIBRARY_PATH, rpath, ldd로 확인).
undefined reference vs multiple definition?선언만 있고 정의가 없음(라이브러리 링크 누락/순서) vs 정의가 둘 이상(헤더에 정의, 전역 중복). 정적 라이브러리는 링크 순서가 중요(의존하는 쪽이 앞). (정적 라이브러리 링크 순서와 심볼 추적)
Makefile 기본 개념?타깃·의존성·레시피, 타임스탬프 기반 증분 빌드, 변수/패턴 규칙, .PHONY.
컴파일 경고 옵션?-Wall -Wextra -Werror, 디버그 -g -O0, 릴리스 -O2, 보안 -fstack-protector-strong -D_FORTIFY_SOURCE=2 -fPIE -pie.
헤더 가드가 필요한 이유?같은 헤더의 중복 포함으로 인한 재정의 오류 방지(#ifndef/#pragma once).
ABI와 API 차이?API: 소스 수준 인터페이스. ABI: 바이너리 수준(호출 규약, 구조체 레이아웃, 심볼). 구조체 레이아웃을 바꾸면 ABI가 깨진다.
정적 분석 / 런타임 검사 도구?컴파일러 경고, clang-tidy/cppcheck/Coverity(정적), ASan/UBSan/TSan/Valgrind(동적), gdb.
gdb 기본 명령?break, run, next/step, bt, print, watch, x, info locals, continue. (Segfault 디버깅 노트)

7. 설계 · 품질 · 실무 경험

질문답변 요지
에러 처리는 어떻게 하나?반환 코드(0 성공 / 음수 실패) + errno, 호출 직후 반드시 검사, 실패 시 지금까지 확보한 자원을 역순으로 정리(goto cleanup 단일 출구). 에러 메시지에 strerror(errno). (실무 C 관례 A2, A14)
goto는 쓰면 안 되나?루프 대용·임의 점프는 금기지만, 다중 자원 정리를 위한 단일 출구(cleanup) 용도는 Linux 커널·curl에서도 쓰는 관례.
모듈화 / 캡슐화를 C에서는?헤더(공개 API)와 소스 분리, 내부는 static, 불투명 포인터, 모듈 접두어(네임스페이스 흉내). (설계와 메모리 기본)
코드 리뷰에서 무엇을 보나?반환값 검사, 자원 해제 경로, 버퍼 경계, 정수 오버플로우, 스레드 안전성, 락 순서, 매직 넘버, 의미 있는 이름, 테스트 가능성.
시큐어 코딩 핵심?입력 검증, 경계 검사, 안전한 문자열 함수, 정수 오버플로우 방지, 최소 권한, 하드코딩된 비밀 금지, 에러 정보 노출 제한. (시큐어코딩 개요)
성능 최적화는 어떻게 접근하나?측정 먼저(perf, gprof, valgrind --tool=callgrind) → 병목 파악 → 알고리즘/자료구조 → 메모리 접근 패턴(캐시 지역성) → 락 경합 → 컴파일러 최적화. 추측 최적화 금지.
캐시 지역성(locality)과 false sharing?연속 메모리 접근이 캐시 적중률을 높인다(배열 > 연결 리스트). 서로 다른 스레드가 쓰는 변수가 같은 캐시 라인(64B)에 있으면 서로 무효화시켜 느려짐 → 패딩/정렬.
데몬 프로세스 작성 요령?fork, setsid, 작업 디렉터리/umask 설정, 표준 fd 닫기(또는 /dev/null), 시그널(SIGTERM 종료/SIGHUP 재로드) 처리, PID 파일, 로그. 현대에는 systemd 서비스로 포그라운드 실행이 보통.
자신 있는 경험을 묻는다면?STAR(상황-과제-행동-결과)로: 장애 원인 추적(core/ASan), 성능 개선 수치, 동시성 문제 해결, 설계 트레이드오프를 구체적 수치와 함께.

면접 준비 팁

  1. “왜”를 설명한다: 정의만 말하지 말고 이유와 트레이드오프까지(예: spinlock은 짧은 구간에서 문맥 전환 비용을 피하려고).
  2. 코드를 손으로 쓸 수 있게: 연결 리스트 뒤집기, 링 버퍼/큐, strcpy/strlen/memcpy/atoi 구현, 비트 연산(카운트/반전), 소켓 서버 골격은 화이트보드에서 바로 쓸 수 있어야 한다.
  3. 경계 조건을 말한다: NULL, 빈 입력, 오버플로우, 에러 경로, 동시성을 먼저 언급하면 깊이가 드러난다.
  4. 모르면 추론하고 단정하지 않는다: 아는 원리에서 출발해 가능성을 말하고, 확인 방법(도구)을 함께 제시한다.

관련 문서